﻿using System;
using System.Collections.Generic;

using System.Text;
using System.Windows.Forms;
using Ivi.Driver.Interop;
using Ivi.ConfigServer.Interop;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace AIRO.Devices.Ccd
{

    [Guid("5C31533A-3327-426e-8A7B-F62479598129")]
    [ClassInterface(ClassInterfaceType.None)]
    [ProgId("AIRO.Devices.Ccd.AvantesCCD")]
    public class AvantesCCD : BasicIviDriver, ICcd, IIviDriver, IIviDriverIdentity, IIviComponentIdentity, IIviDriverOperation, IIviDriverUtility
    { 
        private long m_DeviceHandle;
        private double[] m_Lambda;
        byte[] l_pSaturated;
        ushort m_NrPixels;
        ushort m_StartPixel;
        ushort m_StopPixel;
        ulong m_StartTicks;
        uint m_Measurements;
        uint m_Failures;
        //bool    m_GraphicsDisabled;
        uint m_PreviousTimeStamp;
        short l_NrOfScans = 1;
        AVS5216.AvsIdentityType[] l_Id;
        //int l_Selected;
        string selectedDeviceSerialNumber="";
        private const int WM_USER = 0x400;
        private const int WM_MEAS_READY = WM_USER + 1;
        private const int WM_DBG_INFOAs = WM_USER + 2;
        private const int WM_DEVICE_RESET = WM_USER + 3;
        private Listener listener;       

        private double[] l_pSpectrum;
        int actualNumberOfPixels;

        [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
        private class Listener : NativeWindow
        {
            AvantesCCD ccd;

            public Listener(AvantesCCD ccd)
                : base()
            {
                this.ccd=ccd;
                this.CreateHandle(new CreateParams());
            }



            protected override void WndProc(ref Message a_WinMess)
            {
                ulong l_Ticks = 0;

                if (a_WinMess.Msg == WM_MEAS_READY)
                {
                    if (AVS5216.ERR_SUCCESS == (int)a_WinMess.WParam)
                    {
                        //StatusBar1.Panels[1].Text = "Meas.Status: success";                        
                        if (ccd.m_NrPixels > 0)
                        {
                            ccd.l_pSpectrum = new double[ccd.m_NrPixels];
                            ccd.l_pSaturated = new byte[ccd.m_NrPixels];

                            if (ccd.m_Lambda == null)
                            {
                                ccd.m_Lambda = new double[ccd.m_NrPixels];
                            }

                            if (0 == (int)AVS5216.AVS_GetLambda((IntPtr)ccd.m_DeviceHandle, ref ccd.m_Lambda[0]))
                            {
                                ;
                            }
                            uint l_Time = 0;
                            if (AVS5216.ERR_SUCCESS == (int)AVS5216.AVS_GetScopeData((IntPtr)ccd.m_DeviceHandle, ref l_Time, ref ccd.l_pSpectrum[0]))
                            {
                                uint l_Dif = (l_Time - ccd.m_PreviousTimeStamp) / 100;  //l_Time in 10 us ticks
                                ccd.m_PreviousTimeStamp = l_Time;
                                if (l_Dif != 0)
                                {
                                    //edtLastScanTime.Text = string.Format("{0:F}", l_Dif); //millisec
                                }
                                AVS5216.AVS_GetSaturatedPixels((IntPtr)ccd.m_DeviceHandle, ref ccd.l_pSaturated[0]);
                                ccd.OnMeasurementCompleteEvent(new EventArgs());                                
                            }
                            else
                            {
                                //StatusBar1.Panels[1].Text = "Meas.Status: no data avail.";
                            }

                            ccd.l_pSpectrum = null;
                            ccd.l_pSaturated = null;
                        }
                        ccd.m_Measurements++;
                        /*
                        if (System.Convert.ToInt32(edtNrOfScans.Text) > 0)
                            btnStartMeasurement.Enabled = (System.Convert.ToUInt32(edtNrOfScans.Text) == m_Measurements);
                         * */
                    }
                    else
                    {
                        //StatusBar1.Panels[1].Text = "Meas.Status: failed. Error " + a_WinMess.WParam.ToString();
                        ccd.m_Failures++;
                    }
                    l_Ticks = (ulong)Environment.TickCount;
                    /*
                    edtTotalTime.Text = System.Convert.ToString((l_Ticks - m_StartTicks) / 1000);
                    edtPerformedScans.Text = m_Measurements.ToString();
                    edtAvgTimePerScan.Text = string.Format("{0:F}", ((l_Ticks - m_StartTicks) / m_Measurements));
                    edtFailures.Text = m_Failures.ToString();
                     * */

                    base.WndProc(ref a_WinMess);
                }
                else
                {
                    base.WndProc(ref a_WinMess);
                }
            }

        }

        

        public AvantesCCD()
            : base()
        {
            listener = new Listener(this);
            _description = "IVI Custom Driver for Avantes Spectrometer";
            _supportedInstrumentModels = "Avantes";
            _identifier = "Avantes";
            _vendor = "LNEP ISSP(RAS)";
            _instrumentManufacturer = "Avantes";
        }


        #region ICcd Members

        public CcdState State
        {
            get { throw new NotImplementedException(); }
        }

        public int NumberOfSpectra
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public void StartMeasure()
        {
            

            //short l_NrOfScans = System.Convert.ToInt16(edtNrOfScans.Text);

            //Start Measurement
            m_StartTicks = (ulong)Environment.TickCount;
            m_Measurements = 0;
            m_Failures = 0;
            /*
            edtTotalTime.Text = "0";
            edtPerformedScans.Text = "0";
            edtFailures.Text = "0";
             * */
            

            int l_Res = (int)AVS5216.AVS_Measure((IntPtr)m_DeviceHandle, listener.Handle, l_NrOfScans);
            if (AVS5216.ERR_SUCCESS != l_Res)
            {
                switch (l_Res)
                {
                    case AVS5216.ERR_INVALID_PARAMETER:
                        //StatusBar1.Panels[1].Text = "Meas.Status: invalid parameter";
                        break;
                    default:
                        //StatusBar1.Panels[1].Text = "Meas.Status: start failed, code: " + l_Res.ToString();
                        break;
                }
            }
            else
            {
                //StatusBar1.Panels[1].Text = "Meas.Status: pending";
            }
        }

        private int PrepareMeasurement(AVS5216.MeasConfigType l_PrepareMeasData)
        {
            int l_Res;
            m_StartPixel = l_PrepareMeasData.m_StartPixel;
            m_StopPixel = l_PrepareMeasData.m_StopPixel;
            l_Res = (int)AVS5216.AVS_PrepareMeasure((IntPtr)m_DeviceHandle, ref l_PrepareMeasData);
            if (AVS5216.ERR_SUCCESS != l_Res)
            {
                MessageBox.Show("Error " + l_Res.ToString(), "Avantes",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            //Get Nr Of Scans
            byte a_Enable = 1;
            AVS5216.AVS_UseHighResAdc((IntPtr)m_DeviceHandle, a_Enable);
            return l_Res;
        }
        
        public bool Measure()
        {
            throw new NotImplementedException();
        }
         

        public Array GetFrame(short frameNumber)
        {
            actualNumberOfPixels = m_StopPixel - m_StartPixel + 1;
            double[,] frame = new double[actualNumberOfPixels, 1];
            int size = actualNumberOfPixels*Marshal.SizeOf(typeof(double));
            System.Buffer.BlockCopy(l_pSpectrum,0,frame,0,size);
            

            return frame;
        }

        public int Rank
        {
            get { return 2; }
        }

        public int GetDimension(int dimensionIndx)
        {
            actualNumberOfPixels = m_StopPixel - m_StartPixel + 1;
            return actualNumberOfPixels;
        }

        public event EventHandler<StateChangedEventArgs> StateChangedEvent;

        public event EventHandler<EventArgs> MeasurementCompleteEvent;

        #endregion

        public void Initialize(string ResourceName, bool IdQuery, bool Reset, string OptionString)
        {

            if (!_initialized)
            {
                _rangeCheck = true;
                _cache = true;
                _queryInstrumentStatus = false;
                _simulate = false;
                _driverSetup = "";
                _recordCoercions = false;
                _interchangeCheck = false;
                resourceName = "Not Applicable";
                _logicalName = ResourceName;
                IviDriverSession iviSession = ReadConfigStore();
                try
                {
                    IIviStructure confInitialSettings = (IIviStructure)iviSession.DataComponents["Configurable Initial Settings"];
                    if (confInitialSettings != null)
                    {
                        ConfigureInitialSettings(confInitialSettings);
                    }
                }
                catch (Exception e)
                {

                }

                ParseOptionString(OptionString);
                if (Reset)
                {
                    this.Reset();
                }

                m_DeviceHandle = AVS5216.INVALID_AVS_HANDLE_VALUE;

                #region open communication
                int l_Port = AVS5216.AVS_Init(0);

                AVS5216.AVS_Register(listener.Handle);
                if (l_Port > 0)
                {
                    Debug.WriteLine("Avantes connected: USB");
                    
                    /*
                    StatusBar1.Panels[0].Text = "Connected: USB";
                    btnUpdateList.Enabled = true;
                    sgShowList.Items.Insert(0, "SerialNr");
                    listBox1.Items.Insert(0, "Status");
                    btnUpdateList_Click(this, e);
                     * */
                }
                else
                {
                    AVS5216.AVS_Done();
                    l_Port = AVS5216.AVS_Init(-1);   //try RS-232/bluetooth autodetect
                    //an alternative and faster connection through
                    //RS-232 can be done by specifying the
                    //poortnr in the argument, e.g.
                    //AVS_Init(2) if the device is connected to COM2
                    if (l_Port > 0)
                    {
                        Debug.WriteLine("Avantes connected: RS-232");
                        /*
                        StatusBar1.Panels[0].Text = "Connected: RS-232";
                        btnUpdateList.Enabled = true;
                        sgShowList.Items.Insert(0, "SerialNr");
                        listBox1.Items.Insert(0, "Status");
                        btnUpdateList_Click(this, e);
                         * */
                    }
                    else
                    {
                        //StatusBar1.Panels[0].Text = "Open communication failed";
                        AVS5216.AVS_Done();
                        throw new Exception("Open communication failed");
                    }
                }
                #endregion

                UpdateList();
                Activate(selectedDeviceSerialNumber);
                AVS5216.MeasConfigType l_PrepareMeasData=ReadMeasFromEEProm();
                int l_Res = PrepareMeasurement(l_PrepareMeasData);
                _initialized = true;

            }
            else
            {
                iviDriverErrorQueue.Enqueue(new Error(IviDriver_ErrorCodes.E_IVI_ALREADY_INITIALIZED));
            }


            

        }

        private void UpdateList()
        {
            uint l_Size = 0;
            uint l_RequiredSize = 0;
            int l_NrDevices = 0;

            System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
            /*
            int l_Selected = 0;
            
            l_Selected = sgShowList.SelectedIndex;

            sgShowList.Items.Clear();
            listBox1.Items.Clear();
            sgShowList.Items.Insert(0, "SerialNr");
            listBox1.Items.Insert(0, "Status");
             * */
            l_NrDevices = AVS5216.AVS_GetNrOfDevices();

            l_Id = new AVS5216.AvsIdentityType[l_NrDevices];
            l_RequiredSize = ((uint)l_NrDevices) * (uint)Marshal.SizeOf(typeof(AVS5216.AvsIdentityType)); //Marshal.SizeOf(typeof(PackedRecNative));
            byte[] l_Data = new byte[l_RequiredSize];

            if (l_RequiredSize > 0)
            {
                l_Size = l_RequiredSize;
                l_NrDevices = AVS5216.AVS_GetList(l_Size, ref l_RequiredSize, ref l_Data[0]);//ref l_Data[0]);

                IntPtr aBuffer = Marshal.AllocHGlobal((int)l_Size);
                try
                {
                    for (int i = 0; i < l_NrDevices; i++)
                    {
                        Marshal.Copy(l_Data, (i * (int)Marshal.SizeOf(typeof(AVS5216.AvsIdentityType))), aBuffer, (int)Marshal.SizeOf(typeof(AVS5216.AvsIdentityType)));
                        l_Id[i] = (AVS5216.AvsIdentityType)Marshal.PtrToStructure(aBuffer, typeof(AVS5216.AvsIdentityType));
                    }
                }
                finally
                {
                    Marshal.FreeHGlobal(aBuffer);
                }
                /*
                for (int i = 0; i < l_NrDevices; i++)
                {
                    switch (l_Id[i].m_Status)
                    {
                        case AVS5216.DEVICE_STATUS.UNKNOWN:
                            sgShowList.Items.Add(l_Id[i].m_SerialNumber);// + "\");
                            listBox1.Items.Add("UNKNOWN");
                            break;
                        case AVS5216.DEVICE_STATUS.AVAILABLE:
                            sgShowList.Items.Add(l_Id[i].m_SerialNumber);// + 
                            listBox1.Items.Add("AVAILABLE");
                            break;
                        case AVS5216.DEVICE_STATUS.IN_USE_BY_APPLICATION:
                            sgShowList.Items.Add(l_Id[i].m_SerialNumber);// + 
                            listBox1.Items.Add("IN_USE_BY_APPLICATION");
                            break;
                        case AVS5216.DEVICE_STATUS.IN_USE_BY_OTHER:
                            sgShowList.Items.Add(l_Id[i].m_SerialNumber);// + 
                            listBox1.Items.Add("IN_USE_BY_OTHER");
                            break;
                        default:
                            sgShowList.Items.Add("??????");
                            break;
                    }
                }
                sgShowList.SelectedIndex = l_Selected;
                 * */
            }
            //sgShowList_SelectedIndexChanged(this, e);
        }


        private void Activate(string serialNumber)
        {
            long l_hDevice;
            AVS5216.AvsIdentityType l_Active;
            if (serialNumber != "")
            {


                l_Active = new AVS5216.AvsIdentityType();
                
                //int l_Selected = 0;

                //l_Selected = sgShowList.SelectedIndex;


                l_Active.m_SerialNumber = serialNumber;//sgShowList.Items[l_Selected].ToString();//, sgShowList->Cells[0][sgShowList->Selection.Top].c_str());
            }
            else
            {
                l_Active = l_Id[0];
            }
            l_hDevice = 0;
                l_hDevice = (long)AVS5216.AVS_Activate(ref l_Active);

                if (AVS5216.INVALID_AVS_HANDLE_VALUE == l_hDevice)
                {
                    MessageBox.Show("Error opening device" + l_Active.m_SerialNumber, "Avantes",
                        MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                else
                {
                    m_DeviceHandle = l_hDevice;
                    UpdateList();                   
                    ReadMeasFromEEProm();
                }
            
        }

        private AVS5216.MeasConfigType ReadMeasFromEEProm()
        {
            AVS5216.DeviceConfigType l_pDeviceData = new AVS5216.DeviceConfigType();
            uint l_Size;


            l_Size = (uint)Marshal.SizeOf(typeof(AVS5216.DeviceConfigType));
            byte[] l_Data = new byte[l_Size];

            int l_Res = (int)AVS5216.AVS_GetParameter((IntPtr)m_DeviceHandle, l_Size, ref l_Size, ref l_Data[0]);//l_pDeviceData[0]);
            
            if (l_Res == AVS5216.ERR_INVALID_SIZE)
            {
                l_Data = null;
                l_Data = new byte[l_Size];
                l_Res = (int)AVS5216.AVS_GetParameter((IntPtr)m_DeviceHandle, l_Size, ref l_Size, ref l_Data[0]);//l_pDeviceData[0]);
            }
           
            if (AVS5216.ERR_SUCCESS != l_Res)
            {
                MessageBox.Show("Error ", "Avantes",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);

                throw new Exception("Avantes Error: can't read eeprom");
            }

            
            byte[] a_Fpga = new byte[16];
            byte[] a_As5216 = new byte[16];
            byte[] a_Dll = new byte[16];


            System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();

            l_Res = (int)AVS5216.AVS_GetVersionInfo((IntPtr)m_DeviceHandle, ref a_Fpga[0], ref a_As5216[0], ref a_Dll[0]);

            if (AVS5216.ERR_SUCCESS == l_Res)
            {
                /*
                edtFPGAver.Text = enc.GetString(a_Fpga);
                edtFWver.Text = enc.GetString(a_As5216);
                edtDLLver.Text = enc.GetString(a_Dll);
                 * */
            }

            if (AVS5216.ERR_SUCCESS == (int)AVS5216.AVS_GetNumPixels((IntPtr)m_DeviceHandle, ref m_NrPixels))
            {
                //edtNrOfPixels.Text = System.Convert.ToString(m_NrPixels);
            }


            IntPtr aBuffer = Marshal.AllocHGlobal((int)l_Size);
            try
            {
                Marshal.Copy(l_Data, 0, aBuffer, (int)l_Size);
                l_pDeviceData = (AVS5216.DeviceConfigType)Marshal.PtrToStructure(aBuffer, typeof(AVS5216.DeviceConfigType));
            }
            finally
            {
                Marshal.FreeHGlobal(aBuffer);
            }
            return l_pDeviceData.m_StandAlone.m_Meas;
            
        }

        private void Deactivate()
        {
            AVS5216.AVS_Deactivate((IntPtr)m_DeviceHandle);
            m_DeviceHandle = AVS5216.INVALID_AVS_HANDLE_VALUE;
            UpdateList();
            m_Lambda = null;
        }

        private void StopMeasurement()
        {
            int l_Res = (int)AVS5216.AVS_StopMeasure((IntPtr)m_DeviceHandle);

            if (AVS5216.ERR_SUCCESS != l_Res)
            {
                MessageBox.Show("Error " + l_Res.ToString(), "Avantes",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        public void Close()
        {
            if (_initialized)
            {
                if (m_DeviceHandle != AVS5216.INVALID_AVS_HANDLE_VALUE)
                {
                    int l_Res = (int)AVS5216.AVS_StopMeasure((IntPtr)m_DeviceHandle);

                    if (AVS5216.ERR_SUCCESS != l_Res)
                    {
                        MessageBox.Show("Error in AVS_StopMeasure, code: " + l_Res.ToString(), "Avantes",
                            MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }

                if (m_Lambda != null)
                {
                    m_Lambda = null;
                    m_NrPixels = 0;
                }
                AVS5216.AVS_Done();
                _initialized = false;
            }
            else
            {
                iviDriverErrorQueue.Enqueue(new Error(IviDriver_ErrorCodes.E_IVI_NOT_INITIALIZED));
                throw new Exception("Driver is not initialized");
            }
        }

        protected void ConfigureInitialSettings(IIviStructure confInitialSettings)
        {
            
            foreach (IIviDataComponent dataComp in confInitialSettings.DataComponents)
            {
                string name = dataComp.Name;
                switch (name.ToLower())
                {
                    case "numberofscans":
                        l_NrOfScans = (short)((IIviInteger)dataComp).Value;
                        break;
                    case "deviceSerialNumber":
                        selectedDeviceSerialNumber = ((IIviString)dataComp).Value;
                        break;
                    

                    default:
                        iviDriverErrorQueue.Enqueue(new Error(IviDriver_ErrorCodes.E_IVI_BAD_OPTION_NAME));
                        break;
                }
            }
        }

        public void Disable()
        {
            if (_initialized)
            {
                
            }
            else
            {
                iviDriverErrorQueue.Enqueue(new Error(IviDriver_ErrorCodes.E_IVI_NOT_INITIALIZED));
            }
        }

        protected virtual void OnMeasurementCompleteEvent(EventArgs e)
        {
            EventHandler<EventArgs> handler = MeasurementCompleteEvent;
                if (handler != null)
                {
                    handler(this, e);
                }           
        }


        
    }
}
